/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/models/ink_stroke_model.h>

using namespace journal::model;

#ifndef PACK_TO_BLOB
#define PACK_TO_BLOB(mtype, mbuffer, moffset, mvariable) \
    *reinterpret_cast<mtype*>((char*)mbuffer + moffset) = mvariable;\
    moffset += sizeof(mvariable);
#endif

#ifndef UNPACK_FROM_BLOB
#define UNPACK_FROM_BLOB(mtype, mbuffer, moffset, mvariable) \
    mvariable = *reinterpret_cast<mtype*>((char*)mbuffer + moffset); \
    moffset += sizeof(mvariable);
#endif

ink_stroke_model::ink_stroke_model(
    const b::uint32& typeToken,
    const std::shared_ptr<iink_surface_model>& parent,
    const b::uint32 inkStrokeId
        ) : 
        _parent(parent),
        _points(), 
        _pointCollection(_points), 
        _t0(0),
        _color(bi::ink_color::black),
        _width(0)
{
    // generic_model
    setGenericIdentity(inkStrokeId);
    setGenericTypeToken(typeToken);
    setGenericParent(std::dynamic_pointer_cast<bm::igeneric_model>(parent));
    // tracked_object
    setTrackingService(constants::undoRedoService());

    constants::memoryTrackingService()->logCreate(genericTypeToken(), genericIdentity(), sizeof(ink_stroke_model));
}

ink_stroke_model::~ink_stroke_model()
{
    DEBUG_CONSOLE_TRACE(
        boost::wformat(L"XX ink_stroke_model::~ink_stroke_model() : genericIdentity() = %i") %
        genericIdentity()
        );

    constants::memoryTrackingService()->logDispose(
        genericTypeToken(), 
        genericIdentity(), 
        sizeof(ink_stroke_model) + _points.size() * sizeof(bi::ink_point)
        );
}

std::shared_ptr<ink_stroke_model> ink_stroke_model::create(
    const b::uint32& typeToken,
    const std::shared_ptr<iink_surface_model>& parent,
    const b::uint32 inkStrokeId)
{
    std::shared_ptr<ink_stroke_model> returnValue = std::make_shared<ink_stroke_model>(typeToken, parent, inkStrokeId);
    return returnValue;
}

std::shared_ptr<iink_surface_model> ink_stroke_model::parent()
{
    return _parent.lock();
}

b::Boolean ink_stroke_model::highlighter()
{
    return (_color == bi::ink_color::highlighter) || (_color == bi::ink_color::pink);
}

bi::ink_color ink_stroke_model::color()
{
    return _color;
}

void ink_stroke_model::setColor(const bi::ink_color color)
{
    BAJA_LOCK(_mutex);

    _color = color;
}

baja::float32 ink_stroke_model::width()
{
    return _width;
}

void ink_stroke_model::setWidth(const b::float32 width)
{
    BAJA_LOCK(_mutex);

    _width = width;
}

std::shared_ptr<b::isimple_collection<bi::ink_point>> ink_stroke_model::points()
{
    return b::make_stack_shared_ptr<b::isimple_collection<bi::ink_point>>(&_pointCollection);
}

void ink_stroke_model::addPoint(const m::point2d& position, const b::float32 pressure, const b::uint64 t)
{
    BAJA_LOCK(_mutex);

    if (_points.size() == 0)
    {
        _t0 = t;
    }

    if (_points.size() > 0)
    {
        m::vector2d diff;
        bi::ink_point& lastPoint = _points[_points.size() - 1];
        lastPoint.position.vectorTo(position, &diff);
        b::float32 lastPointDistance = diff.length();
        if (lastPointDistance < constants::ink::pointMinimumDistance())
        {
            lastPoint.t2 = ticksToMs(t - _t0);
            return;
        }
    }

    bi::ink_point newPoint;

    newPoint.position = position;
    newPoint.pressure = pressure;
    newPoint.t1 = static_cast<b::int32>(t);
    newPoint.t2 = -1;

    _points.push_back(newPoint);

    constants::memoryTrackingService()->logExpand(genericTypeToken(), genericIdentity(), sizeof(bi::ink_point));
}

b::uint32 ink_stroke_model::storageId()
{
    return genericIdentity();
}

b::uint32 ink_stroke_model::sizeInMemory()
{
    BAJA_LOCK(_mutex);

    return static_cast<b::uint32>(_points.size()) * sizeof(bi::ink_point) + sizeof(_t0) + sizeof(_color) + sizeof(_width);
}

b::uint32 ink_stroke_model::blobSize()
{
    BAJA_LOCK(_mutex);

    b::uint32 inkSurfaceId = _parent.lock()->storageId();
    b::uint32 inkStrokeId = genericIdentity();

    b::uint32 blobSize = 0;
    blobSize += sizeof(inkStrokeId) + sizeof(inkSurfaceId) + sizeof(_t0) + sizeof(_color) + sizeof(_width);
    blobSize += sizeof(b::uint32);  // points count;
    blobSize += static_cast<b::uint32>(sizeof(bi::ink_point) * _points.size());
    return blobSize;
}

void ink_stroke_model::packToBlob(s::binary_blob& outBlob, b::uint32& offset)
{
    BAJA_LOCK(_mutex);

    b::uint32 inkSurfaceId = _parent.lock()->storageId();
    b::uint32 inkStrokeId = genericIdentity();
    b::uint32 pointsCount = static_cast<b::uint32>(_points.size());

    PACK_TO_BLOB(b::uint32, outBlob._buffer, offset, inkStrokeId);
    PACK_TO_BLOB(b::uint32, outBlob._buffer, offset, inkSurfaceId);
    PACK_TO_BLOB(b::uint64, outBlob._buffer, offset, _t0);
    PACK_TO_BLOB(bi::ink_color, outBlob._buffer, offset, _color);
    PACK_TO_BLOB(b::float32, outBlob._buffer, offset, _width);
    PACK_TO_BLOB(b::uint32, outBlob._buffer, offset, pointsCount);

    b::uint32 pointsSize = sizeof(bi::ink_point) * pointsCount;
    if (pointsSize != 0)
    {
        memcpy(
            (char*)outBlob._buffer + offset,
            &(_points[0]),
            pointsSize
            );
        offset += pointsSize;
    }
}

void ink_stroke_model::unpackFromBlob(const s::binary_blob& inBlob, b::uint32& offset)
{
    BAJA_LOCK(_mutex);

    b::uint32 inkSurfaceId = 0;
    b::uint32 inkStrokeId = 0;
    b::uint32 pointsCount = 0;

    UNPACK_FROM_BLOB(b::uint32, inBlob._buffer, offset, inkStrokeId);
    UNPACK_FROM_BLOB(b::uint32, inBlob._buffer, offset, inkSurfaceId);
    UNPACK_FROM_BLOB(b::uint64, inBlob._buffer, offset, _t0);
    UNPACK_FROM_BLOB(bi::ink_color, inBlob._buffer, offset, _color);
    UNPACK_FROM_BLOB(b::float32, inBlob._buffer, offset, _width);
    UNPACK_FROM_BLOB(b::uint32, inBlob._buffer, offset, pointsCount);

    setGenericIdentity(inkStrokeId);
    _points.resize(pointsCount);

    b::uint32 pointsSize = sizeof(bi::ink_point) * pointsCount;
    memcpy(
        &_points[0],
        (char*)inBlob._buffer + offset,
        pointsSize
        );
    offset += pointsSize;

    constants::memoryTrackingService()->logExpand(genericTypeToken(), genericIdentity(), pointsCount * sizeof(bi::ink_point));
}

#ifdef DEBUG_ENABLE_MODELS
void ink_stroke_model::dump(bool includeChildren, std::wstring prefix)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"%s : genericIdentity() = %i,\t_inkSurfaceId = %i,\t_initialized = %s") %
        prefix.c_str() %
        genericIdentity() %
        _parent.lock()->storageId()
        );

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L"%s :                   \t_t0 = %0.2f, _color = %i, _width = %0.2f, _points = %i, size in memory = %i") %
        prefix.c_str() %
        _t0 %
        static_cast<b::uint32>(_color) %
        _width %
        _points.size() %
        this->sizeInMemory()
        );

    std::wstring arrayPrefex = L"replaceMe";
    std::wstring arrayName = (boost::wformat(L"%sStrokePoints%i") % arrayPrefex % genericIdentity()).str();


    baja::app::debugConsoleTrace(L"\n", TRACE_TAG_RECORDED, true);
    baja::app::debugConsoleTrace(L"\n", TRACE_TAG_RECORDED, true);

    baja::app::debugConsoleTrace(
        (boost::wformat(L"bi::ink_point %s[] = {\n") %
        arrayName
        ).str().c_str(),
        TRACE_TAG_RECORDED,
        true);

    for (auto &inkPoint : _points)
    {
        baja::app::debugConsoleTrace(
            (boost::wformat(L"        {baja::math::point2d(%ff,%ff), %ff, %i, %i},") %
                inkPoint.position.x %
                inkPoint.position.y %
                inkPoint.pressure %
                inkPoint.t1 %
                inkPoint.t2
            ).str().c_str(),
        TRACE_TAG_RECORDED,
        true);
    }

    baja::app::debugConsoleTrace(L"};\n", TRACE_TAG_RECORDED, true);

    std::wstring colorString;
    switch (_color)
    {
    case journal::constants::ink::ink_color::black:
        colorString = L"journal::constants::ink::ink_color::black";
        break;
    case journal::constants::ink::ink_color::blue:
        colorString = L"journal::constants::ink::ink_color::blue";
        break;
    case journal::constants::ink::ink_color::green:
        colorString = L"journal::constants::ink::ink_color::green";
        break;
    case journal::constants::ink::ink_color::highlighter:
        colorString = L"journal::constants::ink::ink_color::highlighter";
        break;
    case journal::constants::ink::ink_color::pink:
        colorString = L"journal::constants::ink::ink_color::pink";
        break;
    case journal::constants::ink::ink_color::pressure:
        colorString = L"journal::constants::ink::ink_color::pressure";
        break;
    case journal::constants::ink::ink_color::red:
        colorString = L"journal::constants::ink::ink_color::red";
        break;
    }
    std::wstring widthString = L"0.015000f";
    baja::app::debugConsoleTrace(
            (boost::wformat(L"symbol_strokes %sStroke%i = {%s, %ff, sizeof(%s) / sizeof(bi::ink_point), %s};\n") %
            arrayPrefex %
            genericIdentity() %
            colorString %
            _width %
            arrayName %
            arrayName
            ).str().c_str(),
        TRACE_TAG_RECORDED,
        true);
}
#endif

baja::uint32 ink_stroke_model::ticksToMs(b::uint64 tTicks)
{
    return static_cast<b::uint32>(tTicks / CLOCKS_PER_SEC);
}

std::shared_ptr<iink_stroke_model> journal::model::createInkStroke(
    const b::uint32& typeToken,
    const std::shared_ptr<iink_surface_model>& parent,
    const b::uint32 inkStrokeId)
{
    std::shared_ptr<ink_stroke_model> returnValue = ink_stroke_model::create(typeToken, parent, inkStrokeId);
    return std::dynamic_pointer_cast<iink_stroke_model>(returnValue);
}